package com.budgetme.graphing;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
import android.widget.LinearLayout;

import com.budgetme.graphing.GraphViewSeries.GraphViewData;
import com.budgetme.graphing.GraphViewSeries.GraphViewSeriesStyle;


abstract public class GraphView extends ImageView {
	private static final String TAG = "GraphView";
	
	private boolean showTitle = true;
	
	private double maxY;
	private double minY;
	private double maxX;
	private double minX;
	
	private boolean manualYAxis;
	private double  manualMaxYValue;
	private double  manualMinYValue;
	
	protected boolean manualYCenter;
	protected double  manualYCenterValue;
	
	static final private class GraphViewConfig {
		static final float BORDER = 20;
		static final float VERTICAL_LABEL_WIDTH = 50;
		static final float HORIZONTAL_LABEL_HEIGHT = 50;
	}

	protected final Paint paint;
	private String[] horlabels;
	private String[] verlabels;
	private String title;
	private final NumberFormat[] numberformatter = new NumberFormat[2];
	private final List<GraphViewSeries> graphSeries;

	private GraphViewStyle mGraphViewStyle;

	public GraphView(Context context, AttributeSet attrs) {
		this(context, attrs.getAttributeValue(null, "title"));
		setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
	}

	public GraphView(Context context, String title) {
		super(context);
		
		setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));

		this.title = (title == null) ? "" : title;
		
		mGraphViewStyle = new GraphViewStyle();
		paint = new Paint();
		graphSeries = new ArrayList<GraphViewSeries>();
		
	}
	
	
	public GraphViewStyle getGraphViewStyle() {
		return mGraphViewStyle;
	}

	public void setGraphViewStyle(GraphViewStyle style) {
		mGraphViewStyle = style;
	}


	public void addSeries(GraphViewSeries series) {
		series.addGraphView(this);
		graphSeries.add(series);
	}


	// TODO: Turn this into a GraphViewStyle setting to manually set
	public String formatLabel(double value, boolean isValueX) {
		int i = isValueX ? 1 : 0;
		if (numberformatter[i] == null) {
			numberformatter[i] = NumberFormat.getNumberInstance();
			double highestvalue = isValueX ? maxX : maxY;
			double lowestvalue = isValueX ? minX : minY;
			if (highestvalue - lowestvalue < 0.1) {
				numberformatter[i].setMaximumFractionDigits(6);
			} else if (highestvalue - lowestvalue < 1) {
				numberformatter[i].setMaximumFractionDigits(4);
			} else if (highestvalue - lowestvalue < 20) {
				numberformatter[i].setMaximumFractionDigits(3);
			} else if (highestvalue - lowestvalue < 100) {
				numberformatter[i].setMaximumFractionDigits(1);
			} else {
				numberformatter[i].setMaximumFractionDigits(0);
			}
		}
		return numberformatter[i].format(value);
	}

	private String[] generateHorlabels(float graphwidth) {
		int numLabels = (int) (graphwidth/GraphViewConfig.VERTICAL_LABEL_WIDTH);
		String[] labels = new String[numLabels+1];
		
		for (int i=0; i<=numLabels; i++) {
			labels[i] = formatLabel(minX + ((maxX-minX)*i/numLabels), true);
		}
		return labels;
	}

	synchronized private String[] generateVerlabels(float graphheight) {
		int numLabels = (int) (graphheight/GraphViewConfig.HORIZONTAL_LABEL_HEIGHT);
		String[] labels = new String[numLabels+1];

		if (maxY == minY) { // if min/max is the same, fake it so that we can render a line
			maxY = maxY*1.05d;
			minY = minY*0.95d;
		}

		for (int i=0; i<=numLabels; i++) {
			labels[numLabels-i] = formatLabel(minY + ((maxY-minY)*i/numLabels), false);
		}
		return labels;
	}

	public double getMaxX() {
		double highest = Integer.MIN_VALUE;
		
		for (int i = 0; i < graphSeries.size(); i++) {
			if (graphSeries.get(i).size() > 0) {
				highest = Math.max(highest, graphSeries.get(i).getMaxX());
			}
		}
		
		if (highest == Integer.MIN_VALUE) return 0;
		return highest;
	}

	public double getMaxY() {
		if (manualYAxis) {
			return manualMaxYValue;
		}
		double highest = Integer.MIN_VALUE;
		
		for (int i = 0; i < graphSeries.size(); i++) {
			if (graphSeries.get(i).size() > 0) {
				highest = Math.max(highest, graphSeries.get(i).getMaxY());
			}
		}
			
		if (highest == Integer.MIN_VALUE) return 0;
		return highest;
	}

	public double getMinX() {
		double lowest = Integer.MAX_VALUE;
		
		for (int i = 0; i < graphSeries.size(); i++) {
			if (graphSeries.get(i).size() > 0) {
				lowest = Math.min(lowest, graphSeries.get(i).getMinX());
			}
		}
		
		if (lowest == Integer.MAX_VALUE) return 0;
		return lowest;
	}


	public double getMinY() {
		if (manualYAxis) {
			return manualMinYValue;
		}
		double lowest = Integer.MAX_VALUE;
		
		for (int i = 0; i < graphSeries.size(); i++) {
			if (graphSeries.get(i).size() > 0) {
				lowest = Math.min(lowest, graphSeries.get(i).getMinY());
			}
		}
			
		if (lowest == Integer.MAX_VALUE) return 0;
		return lowest;
	}

	public void removeSeries(GraphViewSeries series) {
		graphSeries.remove(series);
	}

	public void removeSeries(int index) {
		if (index < 0 || index >= graphSeries.size()) {
			throw new IndexOutOfBoundsException("No series at index " + index);
		}
		graphSeries.remove(index);
	}


	public void setHorizontalLabels(String[] horlabels) {
		this.horlabels = horlabels;
	}


	public void setManualYAxis(boolean manualYAxis) {
		this.manualYAxis = manualYAxis;
	}


	public void setManualYAxisBounds(double max, double min) {
		manualMaxYValue = max;
		manualMinYValue = min;
		manualYAxis = true;
	}
	
	public void setManualYCenter(boolean manualYCenter) {
		this.manualYCenter = manualYCenter;	
	}
	
	public void setManualYCenterValue(double manualYCenterValue) {
		this.manualYCenterValue = manualYCenterValue;
		manualYCenter = true;
	}


	public void setVerticalLabels(String[] verlabels) {
		this.verlabels = verlabels;
	}
	
	public void setShowTitle(boolean showTitle) {
		this.showTitle = showTitle;
	}
	
	// TODO: Should figure out why the -1 on width
	@Override
	protected void onDraw(Canvas canvas) {
		Log.d(TAG, "onDraw");
		
		float height = getHeight();
		float width = getWidth() - 1;		
		
		drawGraph(canvas, height, width);
	}
	
	public void drawGraph(Canvas canvas, float height, float width) {
		
		maxY = getMaxY();
		minY = getMinY();
		maxX = getMaxX();
		minX = getMinX();
		
		double diffX = maxX - minX;
		double diffY = maxY - minY;
		
		float border = GraphViewConfig.BORDER;
		float horstart = 0 + (2 * border);
		
		if (maxY == minY) {
			// if min/max is the same, fake it so that we can render a line
			maxY = maxY*1.05d;
			minY = minY*0.95d;
		}
		
		float graphheight = height - (2 * border);
		float graphwidth = width - (2 * border);

		if (horlabels == null) {
			horlabels = generateHorlabels(graphwidth);
		}
		if (verlabels == null) {
			verlabels = generateVerlabels(graphheight);
		}

		paint.setAntiAlias(true);
		paint.setStrokeWidth(0);
		
		// Horizontal Grid Lines
		int vers = verlabels.length - 1;
		for (int i = 0; i < verlabels.length; i++) {
			paint.setColor(mGraphViewStyle.getGridColor());
			float y = ((graphheight / vers) * i) + border;
			canvas.drawLine(horstart, y, width, y, paint);
			
			paint.setTextAlign(Align.RIGHT);
			
			paint.setColor(mGraphViewStyle.getVerticalLabelsColor());
			canvas.drawText(verlabels[i], horstart - 4, y, paint);
			
		}

		// horizontal labels + Vertical Grid Lines
		int hors = horlabels.length - 1;
		for (int i = 0; i < horlabels.length; i++) {
			paint.setColor(mGraphViewStyle.getGridColor());
			float x = ((graphwidth / hors) * i) + horstart;
			canvas.drawLine(x, height - border, x, border, paint);
			
			// Label alignment, the leftmost label aligns left, rightmost label aligns right, otherwise center
			if (i==horlabels.length-1) {
				paint.setTextAlign(Align.RIGHT);
			} else if (i==0) {
				paint.setTextAlign(Align.LEFT);
			} else {
				paint.setTextAlign(Align.CENTER);
			}
			
			paint.setColor(mGraphViewStyle.getHorizontalLabelsColor());
			canvas.drawText(horlabels[i], x, height - 4, paint);
			
		}

		paint.setTextAlign(Align.CENTER);
		
		if (showTitle) {
			canvas.drawText(title, (graphwidth / 2) + horstart, border - 4, paint);
		}


		paint.setStrokeCap(Paint.Cap.ROUND);

		for (int i=0; i<graphSeries.size(); i++) {
			drawSeries(canvas, graphSeries.get(i).data, graphwidth, graphheight, border, minX, minY, diffX, diffY, horstart, graphSeries.get(i).style);
		}

		
	}
	
	abstract public void drawSeries(Canvas canvas, GraphViewData[] values, float graphwidth, float graphheight, float border, double minX, double minY, double diffX, double diffY, float horstart, GraphViewSeriesStyle style);

}
